# coding=utf-8

# TODO: 1. 多台环境分布式执行
# TODO: 2. DBCheck
# TODO: 3. 邮件展示测试报告HTML

# import unittest
from datetime import datetime
import traceback
# import multiprocessing
from multiprocessing import Pool, current_process
import os
import shutil

from src.report.HTMLTestRunner import HTMLTestRunner
from src.config.config import Config
from src.report.sendemail import sendEmail
from src.runthroughcase.runthroughcase import testPlan
from src.log.log import Log
from src.public.public import Public

logger = Log()  # 创建日志记录对象 logger


# 报告相关信息
class ReportInfo:
    time = datetime.now().strftime("%Y%m%d_%H%M%S")
    reportname = "ResultReport_%s" % time  # 报告名
    reportfullname = reportname + ".html"  # 报告文件名 格式为.html
    reportparentdir = os.path.join(Config.projectDir, "report", reportname)  # 报告文件所在的报告目录，目录名与报告名一致
    reportpath = os.path.join(reportparentdir, reportfullname)  # 同名目录下创建同名报告


# 重制后报告信息
class ReMakeReport:
    startTime = " "
    stopTime = " "
    success_count = 0
    failure_count = 0
    error_count = 0
    title = " "
    description = " "
    result = []


# 重制报告内容
def reMakeReport(results: list):
    startime = datetime.strptime("2999-12-31 23:59:59", r"%Y-%m-%d %H:%M:%S")
    stopime = datetime.strptime("2000-01-01 00:00:01", r"%Y-%m-%d %H:%M:%S")
    total_success_count = 0
    total_failure_count = 0
    total_error_count = 0
    total_results = []
    total_testplan_count = len(results)  # 测试计划配置中启用的案例个数
    for i, _results in enumerate(results):
        total_testsuit_count = len(_results)  # 测试案例中配置的并发案例个数(即Excel中sheet个数)
        logger.debug("串行执行序号：", i)
        for j, result in enumerate(_results):
            logger.debug("  并发序号：", str(j))
            logger.debug("    success_count: ", result.success_count, "type: ", type(result.success_count))
            logger.debug("    failure_count: ", result.failure_count, "type: ", type(result.failure_count))
            logger.debug("    error_count: ", result.error_count, "type: ", type(result.error_count))
            logger.debug("    result: ", result.result, "type: ", type(result.result))
            logger.debug("    stopTime: ", result.stopTime, "type: ", type(result.stopTime))
            logger.debug("    startTime: ", result.startTime, "type: ", type(result.startTime))
            logger.debug("    title: ", result.title, "type: ", type(result.title))
            logger.debug("    description: ", result.description, "type: ", type(result.description))
            dt_startime = result.startTime
            dt_stoptime = result.stopTime
            startime = dt_startime if dt_startime < startime else startime  # 获取更小的时间
            stopime = dt_stoptime if dt_stoptime > stopime else stopime  # 获取更大的时间
            total_success_count += result.success_count
            total_failure_count += result.failure_count
            total_error_count += result.error_count
            total_results.extend(result.result)

    ReMakeReport.startTime = startime
    ReMakeReport.stopTime = stopime
    ReMakeReport.success_count = total_success_count
    ReMakeReport.failure_count = total_failure_count
    ReMakeReport.error_count = total_error_count
    ReMakeReport.title = Config.title
    ReMakeReport.description = Config.description
    ReMakeReport.result = total_results
    logger.debug("*************最终报告内容*************")
    logger.debug("    ReMakeReport.startTime: ", ReMakeReport.startTime)
    logger.debug("    ReMakeReport.stopTime: ", ReMakeReport.stopTime)
    logger.debug("    ReMakeReport.success_count: ", ReMakeReport.success_count)
    logger.debug("    ReMakeReport.failure_count: ", ReMakeReport.failure_count)
    logger.debug("    ReMakeReport.error_count: ", ReMakeReport.error_count)
    logger.debug("    ReMakeReport.title: ", ReMakeReport.title)
    logger.debug("    ReMakeReport.description: ", ReMakeReport.description)
    logger.debug("    ReMakeReport.result: ", ReMakeReport.result)


# 写报告
def writeReport() -> str:
    # 如果配置不生成报告则直接返回None
    if not Config.genReport:
        return
    # 报告相关信息
    reportparentdir = ReportInfo.reportparentdir
    reportpath = ReportInfo.reportpath
    os.mkdir(reportparentdir)
    with open(reportpath, "wb") as fp:
        htmlTestRunner = HTMLTestRunner(stream=fp, title=Config.title, description=Config.description)
        htmlTestRunner.startTime = ReMakeReport.startTime
        htmlTestRunner.stopTime = ReMakeReport.stopTime
        htmlTestRunner.title = ReMakeReport.title
        htmlTestRunner.description = ReMakeReport.description
        htmlTestRunner.generateReport(test="test", result=ReMakeReport)
    # 2018年10月10日 zz：将projectDir/src/css&js/下资源文件copy到报告html相同目录下
    src_css = os.path.join(Config.projectDir, "src", "css&js", "bootstrap.min.css")
    src_js = os.path.join(Config.projectDir, "src", "css&js", "echarts.common.min.js")
    shutil.copy2(src=src_css, dst=reportparentdir)
    shutil.copy2(src=src_js, dst=reportparentdir)
    archivefilename = Public().make_archive(
        base_name=reportparentdir,
        format="zip",
        root_dir=reportparentdir
    )  # 压缩报告
    return archivefilename


# 构造测试集
def makeTestCase():
    try:
        logger.info("构建测试用例开始")
        # 按照Excel配置运行
        allsuites = testPlan()
        logger.info("构建测试用例完毕")
        return allsuites
    except Exception:
        logger.error("测试用例构建失败。报错信息:\n" + traceback.format_exc())


# 每个进程运行方法
def run(p_suite):
    # 2018年9月6日 zz：加上进程号
    # pid = current_process().pid  # 当前进程号
    # 不生成测试报告运行
    # runner = unittest.TextTestRunner()
    # runner.run(p_suite)
    # 生成测试报告运行
    # 2018年9月9日 zz: 增加案例执行失败异常保护
    # 2018年9月17日 zz: 调整为多进程生成一份测试报告
    try:
        runner = HTMLTestRunner(
            # stream=fp,
            # title=Config.title,
            # description=Config.description,
            report=False
        )
        result = runner.run(p_suite)
    except Exception:
        logger.error("案例执行失败。报错信息:\n" + traceback.format_exc())
    return result


# 运行前的初始化工作
def init():
    try:
        logger.info("执行前初始化执行开始")
        tempimgpath = os.path.join(Config.projectDir, "report", "tempimg")  # 截图的临时目录
        if os.path.exists(tempimgpath):
            shutil.rmtree(tempimgpath)
            os.mkdir(tempimgpath)
        else:
            os.mkdir(tempimgpath)
        logger.info("执行前初始化执行完毕")
    except Exception:
        logger.error("测试用例构建失败。报错信息:\n" + traceback.format_exc())


# 发送报告
# 2018年9月16日 zz: 邮件发送单独拿到一个函数中
def sendReport(attchpath: str, info: ReMakeReport):
    """
    :param filepath: 附件路径
    :param info: 报告信息，用于邮件正文展示关键信息
    """
    # 邮件发送
    try:
        if Config.sendEmail:
            logger.info("邮件发送开始")
            sendEmail(attchpath, info)  # 发送邮件
            logger.info("邮件发送结束")
    except Exception:
        logger.error("邮件发送失败。报错信息:\n" + traceback.format_exc())


def main():
    # 运行前的初始化工作
    init()
    # 构造并返回测试集
    allsuites = makeTestCase()
    # 多进程执行开始
    logger.info("测试执行开始")
    # 2018年9月6日 zz: 修改使用pool进程池，使得每个独立的进程可以返回执行结果。
    # 2018年9月17日 zz: 调整为多进程生成一份测试报告
    # for suites in allsuites:
    #     processes = []
    #     for suite in suites:
    #         processes.append(multiprocessing.Process(target=run, args=(suite,)))
    #     for p in processes:
    #         p.start()
    #     for p in processes:
    #         p.join()
    results = []  # 结果集
    # 遍历测试套件, 并使用多进程执行
    for suites in allsuites:  # allsuites <类型 tuple of tuple>
        pool = Pool(len(suites) + 1)
        logger.info("执行测试套件：" + "".join([suite.__repr__() for suite in suites]))
        rl = pool.map(func=run, iterable=suites)  # 返回run方法的返回值
        pool.close()  # 关闭进程池，不再接受新的进程
        pool.join()   # 主进程阻塞等待子进程的退出
        results.append(rl)
    # 重制报告内容
    reMakeReport(results=results)
    # 写报告
    archivefilename = writeReport()
    logger.info("测试执行结束")
    # 发送报告
    sendReport(attchpath=archivefilename, info=ReMakeReport)


# 执行测试集
if __name__ == "__main__":
    main()